﻿using System;
using RayDen.Library.Core;
using RayDen.Library.Core.Components;
using RayDen.Library.Core.Primitives;

namespace RayDen.Library.Components.Surface
{
    [Serializable]
    public class DispersiveGlassMaterial : SurfaceBsdf
    {
        public override BrdfType Type
        {
            get { return BrdfType.Specular; }
        }
        public override bool IsRefractive() { return true; }

        public DispersiveGlassMaterial(RgbSpectrum refl, RgbSpectrum refrct, float outsideIorFact,
                                       float iorFact, bool reflSpecularBounce, bool transSpecularBounce)
        {
            var sp = new RgbSpectrum(0.5f);

            Krefl = RgbSpectrum.Max(ref refl, ref sp); ;
            Krefrct = RgbSpectrum.Max(ref refrct, ref sp); ;
            ousideIor = outsideIorFact;
            ior = iorFact;

            reflectionSpecularBounce = reflSpecularBounce;
            transmitionSpecularBounce = transSpecularBounce;

            float nc = ousideIor;
            float nt = ior;
            float a = nt - nc;
            float b = nt + nc;
            R0 = a * a / (b * b);
        }

        protected override void EvalParams(ref SurfaceTextureData surfaceData)
        {
            var sp = new RgbSpectrum(0.5f);

            Krefl = RgbSpectrum.Max(ref surfaceData.Specular, ref sp); ;
            Krefrct = RgbSpectrum.Max(ref surfaceData.Transmittance, ref sp); ;
            ousideIor = MediumInfo.Air.IoR;
            ior = surfaceData.Medium.IoR;

            var lambda = surfaceData.Throughput.Max();
            var l2 = lambda*lambda;
            float B1=1.0396f, C1=6.0006e-3f, B2=0.231792f, C2=1.0356e-2f;
            float nc = ousideIor;
            float nt = MathLab.Sqrt(Math.Abs(1f + ((B1*l2) / (l2-C1)) + ((B2*l2) / (l2-C2))));
            float a = nt - nc;
            float b = nt + nc;
            R0 = a * a / (b * b);
        }

        public override bool IsDiffuse() { return false; }
        public override bool IsSpecular() { return true; }

        public override void f(ref Vector wo, ref Vector wi, ref Normal N, ref RgbSpectrum in_fs, out RgbSpectrum fs)
        {
            throw new NotSupportedException("Internal error, called GlassMaterial::f()");
        }

        public override float Pdf(ref Vector wo, ref Vector wi, BxDFTypes bxDFType)
        {
            return 1f;
        }

        public override void Sample_f(ref Vector wo, ref Normal Normal, ref Normal shadeNormal, ref RgbSpectrum in_f, float u0, float u1, float u2, ref SurfaceTextureData surfaceData, out BsdfSampleData result)
        {
            result.Lambda = 0f;

            EvalParams(ref surfaceData);
            Assert.IsTrue(!Krefl.IsBlack());
            Assert.IsTrue(!Krefrct.IsBlack());

            Vector rayDir = -wo;
            var N = shadeNormal;
            var shadeN = shadeNormal;
            var wi = new Vector();

            var N1 = N.ToVec();
            var reflDir = rayDir - (2f * (Normal.Dot(ref N, ref rayDir))) * N1;

            // Ray from outside going in ?
            bool into = ((Normal.Dot(ref N, ref shadeN)) > 0);

            float nc = ousideIor;
            bool disperse = in_f.y() > u0;


            float nt = ior + (disperse ? (1f - in_f.y())/10f : 0f);
            if (disperse)
            {
                Krefrct = in_f.MaxAsSingle()*in_f;
                //SampledSpectrum.ReflRainbow[(int) Math.Round((SampledSpectrum.ReflRainbow.Length-1)*u2)];
            }

            float nnt = into ? (nc / nt) : (nt / nc);
            float ddn = (rayDir & shadeN.ToVec());
            float cos2t = 1f - nnt * nnt * (1f - ddn * ddn);
            result.F = RgbSpectrum.UnitSpectrum();
            // Total internal reflection
            if (cos2t < 0f)
            {
                wi = reflDir;
                result.Pdf = 1f;
                result.Type = reflectionSpecularBounce ? BrdfType.Specular : BrdfType.Glossy;
                result.F = Krefl;
                result.Wi = wi;
                return;
            }

            float kk = (into ? 1f : -1f) * (ddn * nnt + MathLab.Sqrt((cos2t)));
            Vector nkk = kk * N1;
            Vector transDir = (nnt * rayDir - nkk).Normalize();

            float c = 1f - (into ? -ddn : (transDir & N1));

            float Re = R0 + (1f - R0) * c * c * c * c * c;
            float Tr = 1f - Re;
            float P = .25f + .5f * Re;

            if (Tr.NearEqual(0f))
            {
                if (Re.NearEqual(0f))
                {
                    result.Pdf = 0f;
                    result.Type = BrdfType.Specular;
                    result.F = new RgbSpectrum(0f);
                }
                else
                {
                    (wi) = reflDir;
                    result.Pdf = 1f;
                    result.Type = reflectionSpecularBounce ? BrdfType.Specular : BrdfType.Glossy;
                    result.F = Krefl;
                }
            }
            else if (Re.NearEqual(0f))
            {
                (wi) = transDir;

                result.Pdf = 1f;
                result.Type = transmitionSpecularBounce ? BrdfType.Specular : BrdfType.Glossy;
                result.F = Krefrct;

            }
            else if (u0 < P)
            {
                (wi) = reflDir;
                result.Pdf = P / Re;
                result.Type = reflectionSpecularBounce ? BrdfType.Specular : BrdfType.Glossy;
                result.F = Krefl;
            }
            else
            {
                if (u0 > u1)
                {
                    //(wi) = SampleTransmissionDirection(u0, u1, ref transDir);
                    wi = transDir;
                    result.F = Krefrct;
                    //SampleTransmission(u1, u2, ref Krefrct);
                }
                else
                {
                    wi = transDir;
                    result.F = Krefrct;
                }

                result.Pdf = (1f - P) / Tr;
                result.Type = transmitionSpecularBounce ? BrdfType.Specular : BrdfType.Glossy;
            }
            result.Wi = wi;
        }

        private FastRandom rnd = new FastRandom();
        private RgbSpectrum SampleTransmission(float u0, float u1, ref RgbSpectrum ks)
        {
            //return new RgbSpectrum(rnd.NextFloat(),rnd.NextFloat(),rnd.NextFloat());
            if (0.9f > (u1 + u0))
            {
                return new RgbSpectrum(0f, 0f, ks.c3);
            }

            if (u0 > u1)
            {
                return new RgbSpectrum(ks.c1, 0f, 0f);
            }

            if (u0 < u1)
            {
                return new RgbSpectrum(0f, ks.c2, 0f);
            }

            return ks;
        }


        private Vector SampleTransmissionDirection(float u0, float u1, ref Vector dir)
        {
            return new Vector(MC.Mutate(u0, u1, dir.x), MC.Mutate(u0, u1, dir.y), MC.Mutate(u0, u1, dir.z));
        }

        public RgbSpectrum GetKrefl() { return Krefl; }
        public RgbSpectrum GetKrefrct() { return Krefrct; }

        private
            RgbSpectrum Krefl, Krefrct;
        public float ousideIor = 1.0004f, ior = 1.5f;
        private float R0;
        public bool reflectionSpecularBounce, transmitionSpecularBounce;
    };
}